package com.gitee.l0km.codegen.base;

import static com.gitee.l0km.com4j.basex.bean.jdk.BeanPropertySupportImpl.BEAN_SUPPORT;

import java.beans.PropertyDescriptor;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitee.l0km.codegen.base.Method.Parameter;
import com.gitee.l0km.com4j.base.Assert;
import com.gitee.l0km.com4j.base.BaseParameterNames;
import com.gitee.l0km.com4j.base.Judge;
import com.gitee.l0km.com4j.base.ParameterNames;
import com.gitee.l0km.com4j.basex.AnnotationProxy;
import com.gitee.l0km.com4j.basex.ReflectionUtils;
import com.gitee.l0km.com4j.basex.reflection.generics.EnhancedTypeResolver;
import com.gitee.l0km.javadocreader.JavadocParameterNames;
import com.gitee.l0km.javadocreader.JavadocReader;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

/**
 * {@link NewSourceInfo}抽象实现
 * @author guyadong
 *
 * @param <T>
 */
public abstract class NewSourceInfoAbstract<T> extends AbstractSchema implements NewSourceInfo<T> {
	private static final Logger logger = LoggerFactory.getLogger(NewSourceInfoAbstract.class);

	protected final Class<T> interfaceClass;
	protected final Class<? extends T> refClass;
	protected final List<Method> methodsNeedGenerated = new ArrayList<Method>();
	protected final BaseParameterNames paramTable;

	protected final Map<String, PropertyDescriptor> properties;
	/**
	 * 当前生成的类型的泛型变量与实际类型参数的映射,非泛型类为空
	 */
	protected final EnhancedTypeResolver typeResolver;

	private final AnnotationHelper annotationHelper = new AnnotationHelper();
	abstract protected void createMethodsNeedGenerated();
	
	protected AnnotationHelper getAnnHelper() {
		return annotationHelper;
	}

	@Override
	protected void addImportedClassFromMethod(Method method, boolean importExceptions, boolean importReturn, boolean importAnnotation) {
		addImportedClass(typeResolver.resolveTypes(method.getGenericParameterTypes()));
		if(importExceptions) {
			addImportedClass(typeResolver.resolveTypes(method.getGenericExceptionTypes()));
		}
		if(importReturn) {
			addImportedClass(typeResolver.resolveType(method.getGenericReturnType()));
		}
	}

	/**
	 * @param interfaceClass
	 * @param refClass
	 *            参考类
	 * @param baseClass
	 *            父类
	 */
	public NewSourceInfoAbstract(Class<T> interfaceClass, Class<? extends T> refClass, Class<? extends T> baseClass) {
		checkClass(interfaceClass,refClass,baseClass);
		this.interfaceClass = interfaceClass;
		this.baseClass = baseClass;
		this.refClass = refClass;

		initImportList();
		try {
			if(refClass != null){
				paramTable = makeBaseParameterNames(refClass);
				typeResolver = new EnhancedTypeResolver(ReflectionUtils.getNormalizedTypeVariables(refClass, true));
			}else if(interfaceClass != null){
				paramTable = makeBaseParameterNames(interfaceClass);
				typeResolver = new EnhancedTypeResolver(Collections.emptyMap());
			}else if(baseClass != null && baseClass.isEnum()){
				// enum support 不需要paramTable
				paramTable = null;
				typeResolver = new EnhancedTypeResolver(Collections.emptyMap());
			}else if(baseClass != null){
				paramTable = makeBaseParameterNames(baseClass);
				typeResolver = new EnhancedTypeResolver(ReflectionUtils.getNormalizedTypeVariables(baseClass, true));
			}else{
				throw new IllegalStateException("cann't construct paramTable instance caused by interfaceClass and refClass both are null");
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		properties = readProperties();
	}
	
	@Override
	protected void initImportList() {
		if (null != baseClass){
			addImportedClass(baseClass);
			addImportedClass(baseClass.getTypeParameters());
		}
		else if(interfaceClass != null){
			addImportedClass(interfaceClass);
			addImportedClass(interfaceClass.getTypeParameters());
		}
	}
	private static BaseParameterNames makeBaseParameterNames(Class<?> clazz) {
		return (clazz.isInterface() || !Modifier.isAbstract(clazz.getModifiers())) && JavadocReader.existSourceOf(clazz) 
				? new JavadocParameterNames(clazz) : new ParameterNames(clazz);
	}
	/**
	 * 检查构造方法传入的参数，子类可以重写此方法
	 * @param interfaceClass
	 * @param refClass
	 * @param baseClass
	 */
	protected void checkClass(Class<T> interfaceClass, Class<? extends T> refClass, Class<? extends T> baseClass){
		Assert.notNull(interfaceClass, "interfaceClass");
		Assert.notNull(refClass, "refClass");
		if (!interfaceClass.isInterface()){
			throw new IllegalArgumentException("interfaceClass must be Interface(必须是接口)");
		}
		if (!interfaceClass.isAssignableFrom(refClass) 
				|| refClass.isInterface()
				|| Modifier.isAbstract(refClass.getModifiers())){
			throw new IllegalArgumentException(String.format(
					"refClass must  implement [%s] and not be abstract class(必须实现接口,且不能是抽象类)", 
					interfaceClass.getName()));
		}
		if (null != baseClass && (!interfaceClass.isAssignableFrom(baseClass) || baseClass.isInterface())) {
			throw new IllegalArgumentException(String.format("baseClass must  implement [%s](必须实现接口)",
					interfaceClass.getName()));
		}
	}

	protected final void addImportedClassFromMethods() {
		Iterator<Method> it = methodsNeedGenerated.iterator();
		while (it.hasNext()) {
			Method im = it.next();
			addImportedClassFromMethod(im, true, true, true);
		}
	}

	@Override
	public Class<T> getInterfaceClass() {
		return interfaceClass;
	}

	@Override
	public List<Method> getMethodsNeedGenerated() {
		if (methodsNeedGenerated.isEmpty()){
			createMethodsNeedGenerated();
		}
		return methodsNeedGenerated;
	}

	protected final static boolean isImplementedMethod(java.lang.reflect.Method im, java.lang.reflect.Method m) {
		if (!Judge.hasNull(im, m)) {
			if ((im.getDeclaringClass().isAssignableFrom(m.getDeclaringClass()) && im.getName().equals(m.getName()))) {
				if (!im.getReturnType().equals(m.getReturnType()))
					return false;
				Class<?>[] params1 = im.getParameterTypes();
				Class<?>[] params2 = m.getParameterTypes();
				if (params1.length == params2.length) {
					for (int i = 0; i < params1.length; i++) {
						if (params1[i] != params2[i])
							return false;
					}
					return true;
				}
			}
		}
		return false;
	}

	protected final static boolean isImplemented(java.lang.reflect.Method[] methods, java.lang.reflect.Method im) {
		return getImplementedMethod(methods,im)!=null;
	}
	
	protected final static boolean isImplemented(Class<?> claz, java.lang.reflect.Method im) {
		return getImplementedMethod(claz.getDeclaredMethods(),im)!=null;
	}
	
	protected final static java.lang.reflect.Method getImplementedMethod(java.lang.reflect.Method[] methods, java.lang.reflect.Method im) {
		for (java.lang.reflect.Method m : methods) {
			if (isImplementedMethod(im, m) && !Modifier.isAbstract(m.getModifiers())) {
				logger.debug("IMPLEMENTED METHOD:{}", im);
				return m;
			}
		}
		return null;
	}

	/**
	 * @return refClass
	 */
	@Override
	public Class<? extends T> getRefClass() {
		return refClass;
	}
	
	@Override
	public boolean compile() {
		boolean compileOk = false;
		try {
			createMethodsNeedGenerated();
			addImportedClassFromMethods();
			compileOk = true;
		} finally {

		}
		return compileOk;
	}

	private static final Method getMethod(java.lang.reflect.Method[] methods, String signature) {
		Assert.notEmpty(signature, "signature");
		for (java.lang.reflect.Method m : methods) {
			if (Method.getSignature(m).equals(signature)){
				ParameterNames pt = new ParameterNames(m.getDeclaringClass());
				try {
					return new Method(m,pt.getParameterNames(m.getName(), m.getParameterTypes()));
				} catch (NoSuchMethodException e) {
					throw new RuntimeException(e);
				}
			}
		}
		return null;
	}
	public final Method getMethod(Class<?> clazz, String signature) {
		Assert.notNull(clazz, "methods");
		return getMethod(clazz.getMethods(),signature);		
	}
	public final Method getDeclaredMethods(Class<?> clazz, String signature) {
		Assert.notNull(clazz, "methods");
		return getMethod(clazz.getDeclaredMethods(),signature);		
	}
	public final Method getMatchedGenericMethod(Class<?> clazz, Method method, Class<?> expectedType,
			String[] genericParamNames, Class<?> targetType) throws MethodException {
		return getMatchedGenericMethod(clazz, method, expectedType, genericParamNames, targetType,null);
	}
	public final Method getMatchedGenericMethod(Class<?> clazz, Method method, Class<?> expectedType,
			String[] genericParamNames, Class<?> targetType,String name) throws MethodException {
		Assert.notNull(clazz, "clzzz");
		Assert.notNull(method, "method");
		Assert.notNull(genericParamNames, "genericParamNames");
		Assert.notNull(targetType, "targetType");
		Assert.notNull(expectedType, "expectedType");
		if(Judge.hasEmpty(genericParamNames))
			throw new MethodException("the genericParamNames's element must not be empty");
		Parameter[] params = method.getParameters();
		Class<?>[] parameterTypes = new Class<?>[params.length];
		for(int i=0;i<parameterTypes.length;i++)
			parameterTypes[i]=params[i].type;
		Set<String> nameSet;
		if (null == (nameSet = CodeGenUtils.toSetIfnotDup(genericParamNames)))
			throw new MethodException("genericParamNames has duplicated elements");
		//代入期望类型
		for (int i = 0; i < parameterTypes.length; i++) {
			if (nameSet.contains(params[i].name)) {
				if (params[i].type != targetType)
					throw new MethodException(String.format(
							"the %s's type that from genericParamNames is not targetType %s", params[i].name,
							targetType.getName()));
				parameterTypes[i]=expectedType;
			}
		}
		try {
			return new Method(getGenericMethod(clazz, name==null||name.isEmpty()?method.getName():name, parameterTypes), method.getParameterNames());
		} catch (NoSuchMethodException e) {
			throw new MethodException(String.format("not found matched generic method for %s",
					method.getDocSignature()));
		}
	}

	protected Map<String, PropertyDescriptor> readProperties() {
		return interfaceClass != null ? BEAN_SUPPORT.getProperties(this.interfaceClass, 0, true) : Collections.<String, PropertyDescriptor>emptyMap();
	}

	/**
	 * @param excludePropertyMethod 是否排除属性的getter/setter方法,为{@code false}时效果同{@link #getMethodsNeedGenerated()}
	 * @return 需要生成的方法列表
	 * @see #getMethodsNeedGenerated()
	 */
	public List<Method> getMethodsNeedGenerated(final boolean excludePropertyMethod) {
		Predicate<Method> p = new Predicate<Method>() {
			@Override
			public boolean apply(Method input) {
				return excludePropertyMethod ? null == propertyDescriptorOf(input) : true;
			}
		};
		Iterable<Method> filtered = Iterables.filter(getMethodsNeedGenerated(),	p);
		return Lists.newArrayList(filtered);
	}

	/**
	 * 返回所有getter/setter方法定义的属性
	 * @return 属性名--PropertyDescriptor 映射,没有返回空
	 */
	public Map<String, PropertyDescriptor> getProperties() {
		return properties;
	}

	public PropertyDescriptor propertyDescriptorOf(Method method) {
		if(null != method){
			for(PropertyDescriptor descriptor:properties.values()){
				if(method.delegate().equals(descriptor.getReadMethod())){
					return descriptor;
				}
				if(method.delegate().equals(descriptor.getWriteMethod())){
					return descriptor;
				}
			}
		}
		return null;
	}

	public boolean isReadable(PropertyDescriptor propertyDescriptor) {
		return propertyDescriptor != null && propertyDescriptor.getReadMethod() != null;
	}

	public boolean isWritable(PropertyDescriptor propertyDescriptor) {
		return propertyDescriptor != null && propertyDescriptor.getWriteMethod() != null;
	}

	public boolean isIs(java.lang.reflect.Method getter) {
	    return (getter != null &&
	            getter.getName().startsWith("is") &&
	            (getter.getReturnType().equals(Boolean.TYPE) ||
	             getter.getReturnType().equals(Boolean.class)));
	}
	/**
	 * 返回参数实际的类型<br>
	 * @param parameter
	 * @since 2.2.8
	 * @see EnhancedTypeResolver#resolveType(Type)
	 */
	public Type actualTypeOf(Parameter parameter) {
		return typeResolver.resolveType(parameter.getGenericType());
	}
	/**
	 * 
	 * @since 2.2.9
	 */
	public EnhancedTypeResolver getTypeResolver() {
		return typeResolver;
	}
	/**
	 * @since 2.3.0
	 */
	public List<AnnotationProxy<?>> annotationOf(Method method) {
		return getAnnHelper().annotationOf(method, "SUPERCLASS");
	}
	/**
	 * @since 2.3.0
	 */
	public List<AnnotationProxy<?>> annotationOf(Parameter parameter) {
		return getAnnHelper().annotationOf(parameter);
	}
	/**
	 * @since 2.3.0
	 */
	public String annoationCodeOf(Parameter parameter, String delim) {
		return getAnnHelper().annoationCodeOf(parameter, delim, fullNameInstanceByImportList);
	}
	/**
	 * @since 2.3.0
	 */
	public String annoationCodeOf(Method method, String delim) {
		return getAnnHelper().annoationCodeOf(method,delim, "SUPERCLASS", fullNameInstanceByImportList);
	}
	/**
	 * @since 2.3.0
	 */
	public List<AnnotationProxy<?>> annotationOf(AnnotatedElement element) {
		return getAnnHelper().annotationOf(element, "SUPERCLASS");
	}

	/**
	 * @since 2.3.0
	 */
	public String annoationCodeOf(AnnotatedElement element, String delim) {
		return getAnnHelper().annoationCodeOf(element,delim, "SUPERCLASS", fullNameInstanceByImportList);
	}
	/**
	 * @since 2.3.0
	 */
	public boolean withAnnotation(Method method,String annotationType) {
		return getAnnHelper().withAnnotation(method, annotationType, "SUPERCLASS");
	}
	/**
	 * @since 2.3.0
	 */
	public boolean withAnnotation(AnnotatedElement element,String annotationType) {
		return getAnnHelper().withAnnotation(element, annotationType, "SUPERCLASS");
	}
	private static final Map<Class<?>, Class<?>> PRIMITIVE_TYPE_MAP = new HashMap<Class<?>, Class<?>>() {
		private static final long serialVersionUID = 2638066380035384674L;
		{
			put(int.class, Integer.class);
			put(boolean.class, Boolean.class);
			put(byte.class, Byte.class);
			put(short.class, Short.class);
			put(char.class, Character.class);
			put(long.class, Long.class);
			put(float.class, Float.class);
			put(double.class, Double.class);
		}
	};
	
	/**
	 * 查找指定的方法
	 * @param clazz 要搜索的类
	 * @param name 方法名
	 * @param parameterTypes 希望匹配的参数类型数组
	 * @throws NoSuchMethodException
	 */
	public static final java.lang.reflect.Method getGenericMethod(Class<?>clazz,String name,Class<?>... parameterTypes) throws NoSuchMethodException{
		List<java.lang.reflect.Method> methods=new ArrayList<java.lang.reflect.Method>();
		//查找同名且参数数目相同的所有方法
		for (java.lang.reflect.Method m : clazz.getMethods())
			if (m.getName().equals(name) && m.getParameterTypes().length == parameterTypes.length)
				methods.add(m);
		if (!methods.isEmpty()) {
			//过滤掉所有不能匹配的方法
			for (int i = 0; i < parameterTypes.length; i++) {
				for (Iterator<java.lang.reflect.Method> it = methods.iterator(); it.hasNext();) {
					if (!isConvert(it.next().getParameterTypes()[i], parameterTypes[i]))
						it.remove();
				}
				if (methods.size() <= 1)
					break;
			}
			if (methods.size() > 1) {
				//如果还有多个方法满足条件，再过滤掉不能直接赋值的方法
				for (int i = 0; i < parameterTypes.length; i++) {
					for (Iterator<java.lang.reflect.Method> it = methods.iterator(); it.hasNext();) {
						if (!isAssignable(it.next().getParameterTypes()[i], parameterTypes[i]))
							it.remove();
					}
					if (methods.size() <= 1)
						break;
				}
			}
			if (methods.size() == 1)
				return methods.iterator().next();
			else if (methods.size() > 1){
				//如果还有多个方法满足条件，再过滤掉类型不相等的方法
				for (int i = 0; i < parameterTypes.length; i++) {
					for (Iterator<java.lang.reflect.Method> it = methods.iterator(); it.hasNext();) {
						if (it.next().getParameterTypes()[i]!= parameterTypes[i])
							it.remove();
					}
					if (methods.size() <= 1)
						break;
				}
				if (methods.size() == 1)
					return methods.iterator().next();
				else	if (methods.size() > 1)
					throw new IllegalStateException("found more matched method");
			}
				
		}
		throw new NoSuchMethodException();
	}
	
	/**
	 * from对象能否则直接赋值给to
	 * @param to
	 * @param from
	 */
	public static final boolean isAssignable(Class<?> to,Class<?>from){
		if (from.isPrimitive()) {
			if (to != from && !to.isAssignableFrom(PRIMITIVE_TYPE_MAP.get(from)))
				return false;
		} else if (!to.isAssignableFrom(from))
			return false;
		return true;
	}

	/**
	 * from对象是否能转换成to
	 * @param to
	 * @param from
	 */
	public static final boolean isConvert(Class<?> to, Class<?> from) {
		if (!isAssignable(to, from)){
			if (to.isPrimitive() && PRIMITIVE_TYPE_MAP.get(to) == from) {
				return true;
			}
			return false;
		}
		return true;
	}
	/**
	 * 判断 {@code refClass}是否实现了{@code interfaceClass}的所有方法
	 * @param interfaceClass
	 * @param refClass
	 */
	public static boolean isFullImplemented(Class<?>interfaceClass,Class<?> refClass){
		if(null == interfaceClass || null == refClass){
			return false;
		}
		if(!interfaceClass.isInterface()){
			throw new IllegalArgumentException(String.format("the interface [%s] is not a interface", 
					interfaceClass.getName()));
		}
		if(refClass.isInterface() || !interfaceClass.isAssignableFrom(refClass)){
			throw new IllegalArgumentException(String.format("refClass must  implement [%s]",	
					refClass.getName()));
		}
		java.lang.reflect.Method[] interfaceMethods = interfaceClass.getMethods();
		java.lang.reflect.Method[] refMethods = refClass.getMethods();
		for(java.lang.reflect.Method im:interfaceMethods){
			if(!isImplemented(refMethods,im)){
				return false;
			}
		}
		return true;
	}
}
